#ifndef UTIL_WAV_FILE_H_
#define UTIL_WAV_FILE_H_
#include <stdio.h>
#include <stdio.h>
#include <string>
#include <sstream>
#include <cstring>
#include <assert.h>
#include <limits.h>

// define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions:
// #define ST_NO_EXCEPTION_HANDLING    1
#ifdef ST_NO_EXCEPTION_HANDLING
    // Exceptions disabled. Throw asserts instead if enabled.
    #define ST_THROW_RT_ERROR(x)    {assert((const char *)x);}
#else
    // use c++ standard exceptions
    #include <stdexcept>
    #include <string>
    #define ST_THROW_RT_ERROR(x)    {throw std::runtime_error(x);}
#endif
#pragma warning(disable: 4996)

#ifndef uint32_t
    typedef unsigned int uint32_t;
#endif


// WAV audio file 'riff' section header
typedef struct {
    char riff_char[4];
    int  package_len;
    char wave[4];
} WavRiff;

// WAV audio file 'format' section header
typedef struct {
    char fmt[4];
    int format_len;
    short fixed;
    short channel_number;
    int sample_rate;
    int byte_rate;
    short byte_per_sample;
    short bits_per_sample;
} WavFormat;

// WAV audio file 'fact' section header
typedef struct {
    char fact_field[4];
    int fact_len;
    uint32_t fact_sample_len;
} WavFact;

// WAV audio file 'data' section header
typedef struct {
    char data_field[4];
    uint32_t data_len;
} WavData;

// WAV audio file header
typedef struct {
    WavRiff riff;
    WavFormat format;
    WavFact fact;
    WavData data;
} WavHeader;

// Base class for processing WAV audio files.
class WavFileBase {
  protected:
    WavFileBase();
    virtual ~WavFileBase();
    // Get pointer to conversion buffer of at min. given size
    void* getConvBuffer(int sizeByte);

  private:
    // Conversion working buffer;
    char* convBuff;
    int convBuffSize;
};

// Class for reading WAV audio files.
class WavInFile : protected WavFileBase {
  public:
    // Constructor: Opens the given WAV file. If the file can't be opened,
    // throws 'runtime_error' exception.
    WavInFile(const char* filename);
    WavInFile(FILE* file);
    ~WavInFile();  // Destructor: Closes the file.
    void Rewind();  // Rewind to beginning of the file
    uint32_t sample_rate() const;  // Get sample rate.
    uint32_t num_bits() const;  // Get number of bits per sample, i.e. 8 or 16.

    // Get sample data size in bytes. Ahem, this should return same information
    // as 'bytes_per_sample'...
    uint32_t datasize_in_bytes() const;
    uint32_t num_samples() const;  // Get total number of samples in file.

    // Get number of bytes per audio sample (e.g. 16bit stereo = 4 bytes/sample)
    uint32_t bytes_per_sample() const;

    // Get number of audio channels in the file (1=mono, 2=stereo)
    uint32_t num_channels() const;

    // Get the audio file length in milliseconds
    uint32_t length_ms() const;

    // Returns how many milliseconds of audio have so far been read from the file
    // \return elapsed duration in milliseconds
    uint32_t elapsed_ms() const;

    // Reads audio samples from the WAV file. This routine works only for 8 bit samples.
    // Reads given number of elements from the file or if end-of-file reached, as many
    // elements as are left in the file.
    //
    // \return Number of 8-bit integers read from the file.
    int Read(unsigned char* buffer, int maxElems);

    // Reads audio samples from the WAV file to 16 bit integer format.
    // Reads given number of elements from the file or if end-of-file reached,
    // as many elements as are left in the file.
    // \return Number of 16-bit integers read from the file.
    // @1: Pointer to buffer where to read data.
    // @2: Size of 'buffer' array (number of array elements).
    int Read(short* buffer, int maxElems);

    // Reads audio samples from the WAV file to floating point format, converting
    // sample values to range [-1,1[. Reads given number of elements from the file
    // or if end-of-file reached, as many elements as are left in the file.
    // Notice that reading in float format supports 8/16/24/32bit sample formats.
    //
    // \return Number of elements read from the file.
    int Read(float* buffer, int maxElems);

    // Check end-of-file.
    //
    // \return Nonzero if end-of-file reached.
    int eof() const;

  private:
    FILE* fp_wav_;  // File pointer.
    bool stream_in_;
    long position;  // Position within the audio stream

    // Counter of how many bytes of sample data have been read from the file.
    long data_read_;

    // WAV header information
    WavHeader header_;

    // Init the WAV file stream
    void Init();

    // Read WAV file headers.
    // \return zero if all ok, nonzero if file format is invalid.
    int ReadWavHeaders();

    /// Checks WAV file header tags.
    /// \return zero if all ok, nonzero if file format is invalid.
    int checkCharTags() const;

    /// Reads a single WAV file header block.
    /// \return zero if all ok, nonzero if file format is invalid.
    int ReadHeaderBlock();

    /// Reads WAV file 'riff' block
    int readRIFFBlock();
};

// Class for writing WAV audio files.
class WavOutFile : protected WavFileBase {
  public:
    // Constructor: Creates a new WAV file. Throws a 'runtime_error' exception
    // if file creation fails.
    WavOutFile(const char* fileName, int sampleRate, int bits, int channels);
    WavOutFile(FILE* file, int sampleRate, int bits, int channels);

    // Destructor: Finalizes & closes the WAV file.
    ~WavOutFile();

    // Write data to WAV file. This function works only with 8bit samples.
    // Throws a 'runtime_error' exception if writing to file fails.
    void Write(const unsigned char* buffer, int numElems);

    // Write data to WAV file. Throws a 'runtime_error' exception if writing to
    // file fails.
    void Write(const short* buffer, int numElems);

    // Write data to WAV file in floating point format, saturating sample values
    // to range [-1..+1[. Throws a 'runtime_error' exception if writing to file
    // fails.
    // @1: Pointer to sample data buffer.
    // @2: How many array items are to be written to file.
    void Write(const float* buffer, int numElems);

  private:
    // Pointer to the WAV file
    FILE* fp_wav_;

    // WAV file header data.
    WavHeader header_;

    // Counter of how many bytes have been written to the file so far.
    int bytesWritten;

    // Fills in WAV file header information.
    void FillInHeader(const uint32_t sampleRate, const uint32_t bits,
                      const uint32_t channels);

    // Finishes the WAV file header by supplementing information of amount of
    // data written to file etc
    void FinishHeader();

    // Writes the WAV file header.
    void WriteHeader();
};

static const char riffStr[] = "RIFF";
static const char waveStr[] = "WAVE";
static const char fmtStr[] = "fmt ";
static const char factStr[] = "fact";
static const char dataStr[] = "data";

//////////////////////////////////////////////////////////////////////////////
// Helper functions for swapping byte order to correctly read/write WAV files
// with big-endian CPU's: Define compile-time defInition _BIG_ENDIAN_ to
// turn-on the conversion if it appears necessary.
//
// For example, Intel x86 is little-endian and doesn't require conversion,
// while PowerPC of Mac's and many other RISC cpu's are big-endian.

#ifdef BYTE_ORDER
    // In gcc compiler detect the byte order automatically
    #if BYTE_ORDER == BIG_ENDIAN
        // big-endian platform.
        #define _BIG_ENDIAN_
    #endif
#endif

#ifdef _BIG_ENDIAN_
// big-endian CPU, swap bytes in 16 & 32 bit words

// helper-function to swap byte-order of 32bit integer
static inline int _swap32(int& dwData) {
    dwData = ((dwData >> 24) & 0x000000FF) |
             ((dwData >> 8) & 0x0000FF00) |
             ((dwData << 8) & 0x00FF0000) |
             ((dwData << 24) & 0xFF000000);
    return dwData;
}

// helper-function to swap byte-order of 16bit integer
static inline short _swap16(short& wData) {
    wData = ((wData >> 8) & 0x00FF) |
            ((wData << 8) & 0xFF00);
    return wData;
}

// helper-function to swap byte-order of buffer of 16bit integers
static inline void _swap16Buffer(short* pData, int numWords) {
    int i;

    for (i = 0; i < numWords; i++) {
        pData[i] = _swap16(pData[i]);
    }
}

#else   // BIG_ENDIAN
// little-endian CPU, WAV file is ok as such

// dummy helper-function
static inline int _swap32(int& dwData) {
    // do nothing
    return dwData;
}

// dummy helper-function
static inline short _swap16(short& wData) {
    // do nothing
    return wData;
}

// dummy helper-function
static inline void _swap16Buffer(short* pData, int numBytes) {
    // do nothing
}
#endif  // BIG_ENDIAN


//////////////////////////////////////////////////////////////////////////////
#endif  // ASR_DECODER_SRC_CORE_BASE_WAV_FILE_H_

